﻿using System;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VsxTools.Common;
using VsxTools.Exceptions;

namespace VsxTools.OutputWindow
{
    public static class OutputWindow
    {
        private static Enums.MissingOutputPaneHandling _missingOutputPaneHandling =
            Enums.MissingOutputPaneHandling.RedirectToDebug;

        public static Enums.MissingOutputPaneHandling MissingOutputPaneHandling
        {
            get { return _missingOutputPaneHandling; }
            set { _missingOutputPaneHandling = value; }
        }

        public static OutputWindowPane General
        {
            get { return GetPane(typeof (GeneralPane)); }
        }

        public static OutputWindowPane Build
        {
            get { return GetPane(typeof (BuildPane)); }
        }

        public static OutputWindowPane Debug
        {
            get { return GetPane(typeof (DebugPane)); }
        }

        public static OutputWindowPane Silent
        {
            get { return GetPane(typeof (SilentPane)); }
        }

        public static OutputWindowPane CreatePane(Type type)
        {
            OutputPaneDefinition paneDefinition = CreatePaneDefinition(type);
            if (paneDefinition == null) return HandleError(type);

            if (!paneDefinition.IsSilent)
            {
                int createSuccess = CreateWindowPane(paneDefinition);
                if (createSuccess != VSConstants.S_OK) return HandleError(type);
            }
            return GetPane(type);
        }

        public static OutputWindowPane GetPane(Type type)
        {
            OutputPaneDefinition paneDefinition = CreatePaneDefinition(type);
            if (paneDefinition == null) return HandleError(type);
            if (paneDefinition.IsSilent) return new OutputWindowPane(paneDefinition, null);

            IVsOutputWindowPane pane;
            int getSuccess = GetWindowPane(paneDefinition, out pane);
            if (getSuccess != VSConstants.S_OK || pane == null)
            {
                int createSuccess = CreateWindowPane(paneDefinition);
                if (createSuccess != VSConstants.S_OK) return HandleError(type);

                getSuccess = GetWindowPane(paneDefinition, out pane);
                if (getSuccess != VSConstants.S_OK || pane == null) return HandleError(type);
            }

            return new OutputWindowPane(paneDefinition, pane);
        }

        public static bool DeletePane(Type type)
        {
            OutputPaneDefinition paneDefinition = CreatePaneDefinition(type);
            if (paneDefinition == null) HandleError(type);
            return DeleteWindowPane(paneDefinition) == VSConstants.S_OK;
        }

        #region Output pane definitions

        private sealed class GeneralPane : OutputPaneDefinition
        {
            public override Guid Guid
            {
                get { return VSConstants.GUID_OutWindowGeneralPane; }
            }
        }

        private sealed class DebugPane : OutputPaneDefinition
        {
            public override Guid Guid
            {
                get { return VSConstants.GUID_OutWindowDebugPane; }
            }
        }

        private sealed class BuildPane : OutputPaneDefinition
        {
            public override Guid Guid
            {
                get { return VSConstants.GUID_BuildOutputWindowPane; }
            }
        }

        private sealed class SilentPane : OutputPaneDefinition
        {
            public SilentPane()
            {
                IsSilent = true;
            }
        }

        #endregion

        private static IVsOutputWindow OutputWindowInstance
        {
            get { return Package.GetGlobalService(typeof (SVsOutputWindow)) as IVsOutputWindow; }
        }

        private static OutputPaneDefinition CreatePaneDefinition(Type type)
        {
            OutputPaneDefinition paneDefinition = null;
            try
            {
                paneDefinition = Activator.CreateInstance(type) as OutputPaneDefinition;
            }
            catch (SystemException)
            {
            }
            return paneDefinition;
        }

        private static int CreateWindowPane(OutputPaneDefinition paneDefinition)
        {
            Guid paneGuid = paneDefinition.Guid;
            return OutputWindowInstance.CreatePane(ref paneGuid, paneDefinition.Name,
                paneDefinition.InitiallyVisible ? -1 : 0, paneDefinition.ClearWithSolution ? -1 : 0);
        }

        private static int GetWindowPane(OutputPaneDefinition paneDefinition, out IVsOutputWindowPane pane)
        {
            Guid paneGuid = paneDefinition.Guid;
            return OutputWindowInstance.GetPane(ref paneGuid, out pane);
        }

        private static int DeleteWindowPane(OutputPaneDefinition paneDefinition)
        {
            Guid paneGuid = paneDefinition.Guid;
            return OutputWindowInstance.DeletePane(ref paneGuid);
        }

        private static OutputWindowPane HandleError(Type type)
        {
            OutputWindowPane newPane;
            switch (_missingOutputPaneHandling)
            {
                case Enums.MissingOutputPaneHandling.ThrowException:
                    throw new WindowPaneNotFoundException(type);
                case Enums.MissingOutputPaneHandling.Silent:
                    newPane = Silent;
                    break;
                case Enums.MissingOutputPaneHandling.RedirectToGeneral:
                    newPane = General;
                    break;
                default:
                    newPane = Debug;
                    break;
            }

            if (newPane != null) newPane.Activate();
            return newPane;
        }
    }
}
